//File   : sndwav_common.c  
//Author : Loon <sepnic@gmail.com>  
  
#include <assert.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <alsa/asoundlib.h>  
  
#include "sndwav_common.h"  
  
int SNDWAV_P_GetFormat(WAVContainer_t *wav, snd_pcm_format_t *snd_format)  
{     
    if (LE_SHORT(wav->format.format) != WAV_FMT_PCM)  
        return -1;  
      
    switch (LE_SHORT(wav->format.sample_length)) {  
    case 16:  
        *snd_format = SND_PCM_FORMAT_S16_LE;  
        break;  
    case 8:  
        *snd_format = SND_PCM_FORMAT_U8;  
        break;  
    default:  
        *snd_format = SND_PCM_FORMAT_UNKNOWN;  
        break;  
    }  
  
    return 0;  
}  
  
ssize_t SNDWAV_ReadPcm(SNDPCMContainer_t *sndpcm, size_t rcount)  
{  
    ssize_t r;  
    size_t result = 0;  
    size_t count = rcount;  
    uint8_t *data = sndpcm->data_buf;  
  
    if (count != sndpcm->chunk_size) {  
        count = sndpcm->chunk_size;  
    }  
  
    while (count > 0) {  
        r = snd_pcm_readi(sndpcm->handle, data, count);  
          
        if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {  
            snd_pcm_wait(sndpcm->handle, 1000);  
        } else if (r == -EPIPE) {  
            snd_pcm_prepare(sndpcm->handle);  
            fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>/n");  
        } else if (r == -ESTRPIPE) {  
            fprintf(stderr, "<<<<<<<<<<<<<<< Need suspend >>>>>>>>>>>>>>>/n");  
        } else if (r < 0) {  
            fprintf(stderr, "Error snd_pcm_writei: [%s]", snd_strerror(r));  
            exit(-1);  
        }  
          
        if (r > 0) {  
            result += r;  
            count -= r;  
            data += r * sndpcm->bits_per_frame / 8;  
        }  
    }  
    return rcount;  
}  
  
ssize_t SNDWAV_WritePcm(SNDPCMContainer_t *sndpcm, size_t wcount)  
{  
    ssize_t r;  
    ssize_t result = 0;  
    uint8_t *data = sndpcm->data_buf;  
  
    if (wcount < sndpcm->chunk_size) {  
        snd_pcm_format_set_silence(sndpcm->format,   
            data + wcount * sndpcm->bits_per_frame / 8,   
            (sndpcm->chunk_size - wcount) * sndpcm->channels);  
        wcount = sndpcm->chunk_size;  
    }  
    while (wcount > 0) {  
        r = snd_pcm_writei(sndpcm->handle, data, wcount);  
        if (r == -EAGAIN || (r >= 0 && (size_t)r < wcount)) {  
            snd_pcm_wait(sndpcm->handle, 1000);  
        } else if (r == -EPIPE) {  
            snd_pcm_prepare(sndpcm->handle);  
            fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>/n");  
        } else if (r == -ESTRPIPE) {              
            fprintf(stderr, "<<<<<<<<<<<<<<< Need suspend >>>>>>>>>>>>>>>/n");          
        } else if (r < 0) {  
            fprintf(stderr, "Error snd_pcm_writei: [%s]", snd_strerror(r));  
            exit(-1);  
        }  
        if (r > 0) {  
            result += r;  
            wcount -= r;  
            data += r * sndpcm->bits_per_frame / 8;  
        }  
    }  
    return result;  
}  
  
int SNDWAV_SetParams(SNDPCMContainer_t *sndpcm, WAVContainer_t *wav)  
{  
    snd_pcm_hw_params_t *hwparams;  
    snd_pcm_format_t format;  
    uint32_t exact_rate;  
    uint32_t buffer_time, period_time;  
  
    /* Allocate the snd_pcm_hw_params_t structure on the stack. */  
    snd_pcm_hw_params_alloca(&hwparams);  
      
    /* Init hwparams with full configuration space */  
    if (snd_pcm_hw_params_any(sndpcm->handle, hwparams) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_any/n");  
        goto ERR_SET_PARAMS;  
    }  
  
    if (snd_pcm_hw_params_set_access(sndpcm->handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_set_access/n");  
        goto ERR_SET_PARAMS;  
    }  
  
    /* Set sample format */  
    if (SNDWAV_P_GetFormat(wav, &format) < 0) {  
        fprintf(stderr, "Error get_snd_pcm_format/n");  
        goto ERR_SET_PARAMS;  
    }  
    if (snd_pcm_hw_params_set_format(sndpcm->handle, hwparams, format) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_set_format/n");  
        goto ERR_SET_PARAMS;  
    }  
    sndpcm->format = format;  
  
    /* Set number of channels */  
    if (snd_pcm_hw_params_set_channels(sndpcm->handle, hwparams, LE_SHORT(wav->format.channels)) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_set_channels/n");  
        goto ERR_SET_PARAMS;  
    }  
    sndpcm->channels = LE_SHORT(wav->format.channels);  
  
    /* Set sample rate. If the exact rate is not supported */  
    /* by the hardware, use nearest possible rate.         */   
    exact_rate = LE_INT(wav->format.sample_rate);  
    if (snd_pcm_hw_params_set_rate_near(sndpcm->handle, hwparams, &exact_rate, 0) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_set_rate_near/n");  
        goto ERR_SET_PARAMS;  
    }  
    if (LE_INT(wav->format.sample_rate) != exact_rate) {  
        fprintf(stderr, "The rate %d Hz is not supported by your hardware./n ==> Using %d Hz instead./n",   
            LE_INT(wav->format.sample_rate), exact_rate);  
    }  
  
    if (snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_get_buffer_time_max/n");  
        goto ERR_SET_PARAMS;  
    }  
    if (buffer_time > 500000) buffer_time = 500000;  
    period_time = buffer_time / 4;  
  
    if (snd_pcm_hw_params_set_buffer_time_near(sndpcm->handle, hwparams, &buffer_time, 0) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_set_buffer_time_near/n");  
        goto ERR_SET_PARAMS;  
    }  
  
    if (snd_pcm_hw_params_set_period_time_near(sndpcm->handle, hwparams, &period_time, 0) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params_set_period_time_near/n");  
        goto ERR_SET_PARAMS;  
    }  
  
    /* Set hw params */  
    if (snd_pcm_hw_params(sndpcm->handle, hwparams) < 0) {  
        fprintf(stderr, "Error snd_pcm_hw_params(handle, params)/n");  
        goto ERR_SET_PARAMS;  
    }  
  
    snd_pcm_hw_params_get_period_size(hwparams, &sndpcm->chunk_size, 0);   
    snd_pcm_hw_params_get_buffer_size(hwparams, &sndpcm->buffer_size);  
    if (sndpcm->chunk_size == sndpcm->buffer_size) {        
        fprintf(stderr, ("Can't use period equal to buffer size (%lu == %lu)/n"), sndpcm->chunk_size, sndpcm->buffer_size);         
        goto ERR_SET_PARAMS;  
    }  
  
    sndpcm->bits_per_sample = snd_pcm_format_physical_width(format);  
    sndpcm->bits_per_frame = sndpcm->bits_per_sample * LE_SHORT(wav->format.channels);  
      
    sndpcm->chunk_bytes = sndpcm->chunk_size * sndpcm->bits_per_frame / 8;  
  
    /* Allocate audio data buffer */  
    sndpcm->data_buf = (uint8_t *)malloc(sndpcm->chunk_bytes);  
    if (!sndpcm->data_buf) {  
        fprintf(stderr, "Error malloc: [data_buf]/n");  
        goto ERR_SET_PARAMS;  
    }  
  
    return 0;  
  
ERR_SET_PARAMS:  
    return -1;  
}  

